<!DOCTYPE html>



  


<html class="theme-next gemini use-motion" lang="zh-Hans">
<head>
  <meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
<meta name="theme-color" content="#222">









<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
















  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />







<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />

<link href="/css/main.css?v=5.1.4" rel="stylesheet" type="text/css" />


  <link rel="apple-touch-icon" sizes="180x180" href="/%E5%8D%9A%E5%AE%A2.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="32x32" href="/images/%E5%8D%9A%E5%AE%A2.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="16x16" href="/images/%E5%8D%9A%E5%AE%A2.png?v=5.1.4">


  <link rel="mask-icon" href="/images/logo.svg?v=5.1.4" color="#222">





  <meta name="keywords" content="Mybatis," />





  <link rel="alternate" href="/atom.xml" title="Createsequence's Blog" type="application/atom+xml" />






<meta name="description" content="概述 根据上一篇文章，我们了解了 Mybatis 的配置文件是如何被加载的。在完成这一步之后，Mybatis 还需要根据配置文件中指定的路径去加载 Mapper 接口与写有 sql 的相应映射文件，最终完成两者的绑定。 在本篇文章，我们将结合源码阐述这个过程。 这是关于 Mybatis 的第二篇文章，前文：  Mybatis源码学习（一）：配置文件的加载  一、加载接口  1.XMLConfigB">
<meta property="og:type" content="article">
<meta property="og:title" content="Mybatis源码学习（二）：Mapper接口的绑定">
<meta property="og:url" content="http://blog.xiajibagao.top/2021/06/14/mybatis/Mybatis%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9AMapper%E6%8E%A5%E5%8F%A3%E7%9A%84%E7%BB%91%E5%AE%9A/index.html">
<meta property="og:site_name" content="Createsequence&#39;s Blog">
<meta property="og:description" content="概述 根据上一篇文章，我们了解了 Mybatis 的配置文件是如何被加载的。在完成这一步之后，Mybatis 还需要根据配置文件中指定的路径去加载 Mapper 接口与写有 sql 的相应映射文件，最终完成两者的绑定。 在本篇文章，我们将结合源码阐述这个过程。 这是关于 Mybatis 的第二篇文章，前文：  Mybatis源码学习（一）：配置文件的加载  一、加载接口  1.XMLConfigB">
<meta property="og:locale">
<meta property="og:image" content="http://img.xiajibagao.top/image-20210609000011106.png">
<meta property="og:image" content="http://img.xiajibagao.top/image-20210619202948385.png">
<meta property="article:published_time" content="2021-06-13T16:00:00.000Z">
<meta property="article:modified_time" content="2021-06-21T15:35:35.583Z">
<meta property="article:author" content="Createsequence">
<meta property="article:tag" content="Mybatis">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="http://img.xiajibagao.top/image-20210609000011106.png">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '',
    scheme: 'Gemini',
    version: '5.1.4',
    sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":true,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="http://blog.xiajibagao.top/2021/06/14/mybatis/Mybatis源码学习（二）：Mapper接口的绑定/"/>





  <title>Mybatis源码学习（二）：Mapper接口的绑定 | Createsequence's Blog</title>
  








<meta name="generator" content="Hexo 5.2.0"></head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">

  
  
    
  

  <div class="container sidebar-position-left page-post-detail">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/"  class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">Createsequence's Blog</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle">一个努力前进的程序猿</p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br />
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-tags">
          <a href="/tags/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-tags"></i> <br />
            
            标签
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/categories/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-th"></i> <br />
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/archives/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br />
            
            归档
          </a>
        </li>
      

      
        <li class="menu-item menu-item-search">
          
            <a href="javascript:;" class="popup-trigger">
          
            
              <i class="menu-item-icon fa fa-search fa-fw"></i> <br />
            
            搜索
          </a>
        </li>
      
    </ul>
  

  
    <div class="site-search">
      
  <div class="popup search-popup local-search-popup">
  <div class="local-search-header clearfix">
    <span class="search-icon">
      <i class="fa fa-search"></i>
    </span>
    <span class="popup-btn-close">
      <i class="fa fa-times-circle"></i>
    </span>
    <div class="local-search-input-wrapper">
      <input autocomplete="off"
             placeholder="搜索..." spellcheck="false"
             type="text" id="local-search-input">
    </div>
  </div>
  <div id="local-search-result"></div>
</div>



    </div>
  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="http://blog.xiajibagao.top/2021/06/14/mybatis/Mybatis%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9AMapper%E6%8E%A5%E5%8F%A3%E7%9A%84%E7%BB%91%E5%AE%9A/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/Createsequence.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Createsequence's Blog">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">Mybatis源码学习（二）：Mapper接口的绑定</h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2021-06-14T00:00:00+08:00">
                2021-06-14
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Mybatis/" itemprop="url" rel="index">
                    <span itemprop="name">Mybatis</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          
            <div class="post-wordcount">
              
                
                <span class="post-meta-item-icon">
                  <i class="fa fa-file-word-o"></i>
                </span>
                
                  <span class="post-meta-item-text">字数统计&#58;</span>
                
                <span title="字数统计">
                  4.6k
                </span>
              

              

              
            </div>
          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        <h2 id="概述">概述</h2>
<p>根据上一篇文章，我们了解了 Mybatis 的配置文件是如何被加载的。在完成这一步之后，Mybatis 还需要根据配置文件中指定的路径去加载 Mapper 接口与写有 sql 的相应映射文件，最终完成两者的绑定。</p>
<p>在本篇文章，我们将结合源码阐述这个过程。</p>
<p>这是关于 Mybatis 的第二篇文章，前文：</p>
<blockquote>
<p><a href="https://blog.xiajibagao.top/2021/06/05/mybatis/Mybatis%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%B8%80%EF%BC%89%EF%BC%9A%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E7%9A%84%E5%8A%A0%E8%BD%BD/">Mybatis源码学习（一）：配置文件的加载</a></p>
</blockquote>
<h2 id="一-加载接口">一、加载接口</h2>
<p><img src="http://img.xiajibagao.top/image-20210609000011106.png" alt="image-20210609000011106"></p>
<h3 id="1xmlconfigbuilder">1.XMLConfigBuilder</h3>
<p>见名知意，这个类的作用在于<strong>解析映射文件</strong>，根据上文，我们会了解到他里面有各种 <code>标签名+Element</code>为名的解析方法，其中，针对 Mapper 映射文件解析方法就是<code>mapperElement()</code>。</p>
<p>在开始之前，我们需要知道，在映射文件中有四种指定 Mybatis 接口的方式：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">mappers</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!--需保证接口与xml同包同名--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">package</span> <span class="attr">name</span>=<span class="string">&quot;com.huang.read.base&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">mapper</span> <span class="attr">class</span>=<span class="string">&quot;com.huang.read.base.PersonDao&quot;</span> /&gt;</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">&lt;!--不需要需保证接口与xml同包同名--&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">mapper</span> <span class="attr">resource</span>=<span class="string">&quot;com/huang/read/base/PersonDao.xml&quot;</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">mapper</span> <span class="attr">url</span>=<span class="string">&quot;C:\document\GitProject\mybatis-3\src\main\java\com\huang\read\base\PersonDao.xml&quot;</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">mappers</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>这决定了获取在<code>XMLConfigBuilder</code>中解析 Mapper 映射文件的方法<code>mapperElement()</code>会有四种不同处理逻辑：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">mapperElement</span><span class="params">(XNode parent)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (parent != <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">for</span> (XNode child : parent.getChildren()) &#123;</span><br><span class="line">            <span class="comment">// 1. 如果是通过package标签配置整包扫描</span></span><br><span class="line">            <span class="keyword">if</span> (<span class="string">&quot;package&quot;</span>.equals(child.getName())) &#123;</span><br><span class="line">                String mapperPackage = child.getStringAttribute(<span class="string">&quot;name&quot;</span>);</span><br><span class="line">                <span class="comment">// 注册扫描到的接口</span></span><br><span class="line">                configuration.addMappers(mapperPackage);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123; <span class="comment">// 如果是通过mapper标签配置</span></span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 获取标签上的resource、url和class属性</span></span><br><span class="line">                String resource = child.getStringAttribute(<span class="string">&quot;resource&quot;</span>);</span><br><span class="line">                String url = child.getStringAttribute(<span class="string">&quot;url&quot;</span>);</span><br><span class="line">                String mapperClass = child.getStringAttribute(<span class="string">&quot;class&quot;</span>);</span><br><span class="line">                </span><br><span class="line">                <span class="comment">// 2.使用resource</span></span><br><span class="line">                <span class="keyword">if</span> (resource != <span class="keyword">null</span> &amp;&amp; url == <span class="keyword">null</span> &amp;&amp; mapperClass == <span class="keyword">null</span>) &#123;</span><br><span class="line">                    ErrorContext.instance().resource(resource);</span><br><span class="line">                    <span class="keyword">try</span>(InputStream inputStream = Resources.getResourceAsStream(resource)) &#123;</span><br><span class="line">                        XMLMapperBuilder mapperParser = <span class="keyword">new</span> XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());</span><br><span class="line">                        <span class="comment">// 注册扫描到的接口</span></span><br><span class="line">                        mapperParser.parse();</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125; <span class="keyword">else</span> <span class="keyword">if</span> (resource == <span class="keyword">null</span> &amp;&amp; url != <span class="keyword">null</span> &amp;&amp; mapperClass == <span class="keyword">null</span>) &#123;</span><br><span class="line">                    <span class="comment">// 3.使用url</span></span><br><span class="line">                    </span><br><span class="line">                    ErrorContext.instance().resource(url);</span><br><span class="line">                    <span class="keyword">try</span>(InputStream inputStream = Resources.getUrlAsStream(url))&#123;</span><br><span class="line">                        XMLMapperBuilder mapperParser = <span class="keyword">new</span> XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());</span><br><span class="line">                        <span class="comment">// 注册扫描到的接口</span></span><br><span class="line">                        mapperParser.parse();</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125; <span class="keyword">else</span> <span class="keyword">if</span> (resource == <span class="keyword">null</span> &amp;&amp; url == <span class="keyword">null</span> &amp;&amp; mapperClass != <span class="keyword">null</span>) &#123;</span><br><span class="line">                    <span class="comment">// 4.使用class</span></span><br><span class="line">                    </span><br><span class="line">                    Class&lt;?&gt; mapperInterface = Resources.classForName(mapperClass);</span><br><span class="line">                    <span class="comment">// 注册扫描到的接口</span></span><br><span class="line">                    configuration.addMapper(mapperInterface);</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="keyword">throw</span> <span class="keyword">new</span> BuilderException(<span class="string">&quot;A mapper element may only specify a url, resource or class, but not more than one.&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="2mapperregistry">2.MapperRegistry</h3>
<p><code>MapperRegistry</code>类在<code>Configuration</code>中。当我们使用<code>addMapper()</code>和 <code>addMappers()</code>将一个映射文件或者多个映射文件接口添加到配置的时候，最终配置会添加到<code>Configuration</code>中一个<code>MapperRegistry</code>实例里。</p>
<p><strong>他存放了接口与其对应的映射文件的各种信息，当接口和通过映射文件生产的实现类代理通常一起添加到注册表中时，也就完成了绑定</strong>。</p>
<p><code>MapperRegistry</code>内部维护了一个以 Mapper 接口的类作为 key ，接口实现类工厂——暂时先这么理解——作为的 value 的 HashMap 集合 <code>knownMappers</code>，一个代理工厂中可以生产接口中的所有方法实现：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Map&lt;Class&lt;?&gt;, MapperProxyFactory&lt;?&gt;&gt; knownMappers = <span class="keyword">new</span> HashMap&lt;&gt;();</span><br></pre></td></tr></table></figure>
<p>当我们想将一个 package 下的所有 Mapper 接口添加到 <code>knownMappers</code>中时，会调用 <code>MapperRegistry.addMappers()</code>：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addMappers</span><span class="params">(String packageName, Class&lt;?&gt; superType)</span> </span>&#123;</span><br><span class="line">    ResolverUtil&lt;Class&lt;?&gt;&gt; resolverUtil = <span class="keyword">new</span> ResolverUtil&lt;&gt;();</span><br><span class="line">    <span class="comment">// 扫描指定包下的所有类，并将属于superType的类保留下来</span></span><br><span class="line">    resolverUtil.find(<span class="keyword">new</span> ResolverUtil.IsA(superType), packageName);</span><br><span class="line">    <span class="comment">// 获取类集合</span></span><br><span class="line">    Set&lt;Class&lt;? extends Class&lt;?&gt;&gt;&gt; mapperSet = resolverUtil.getClasses();</span><br><span class="line">    <span class="comment">// 遍历集合并将类加入knownMappers</span></span><br><span class="line">    <span class="keyword">for</span> (Class&lt;?&gt; mapperClass : mapperSet) &#123;</span><br><span class="line">        addMapper(mapperClass);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>通过<code>ResolverUtil</code>工具类获取指定包下的所有接口类后，遍历并调用——实际上直接添加单个接口类也是调用这个——<code>addMapper(Class&lt;T&gt; type)</code>方法将之一个一个的添加到<code>knownMappers</code>：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> &lt;T&gt; <span class="function"><span class="keyword">void</span> <span class="title">addMapper</span><span class="params">(Class&lt;T&gt; type)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 判断是否为接口</span></span><br><span class="line">    <span class="keyword">if</span> (type.isInterface()) &#123;</span><br><span class="line">        <span class="comment">// 判断这个Mapper接口是否已经加入了knownMappers</span></span><br><span class="line">        <span class="keyword">if</span> (hasMapper(type)) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> BindingException(<span class="string">&quot;Type &quot;</span> + type + <span class="string">&quot; is already known to the MapperRegistry.&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">boolean</span> loadCompleted = <span class="keyword">false</span>;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            </span><br><span class="line">            knownMappers.put(type, <span class="keyword">new</span> MapperProxyFactory&lt;&gt;(type));</span><br><span class="line">            <span class="comment">// 解析映射文件</span></span><br><span class="line">            MapperAnnotationBuilder parser = <span class="keyword">new</span> MapperAnnotationBuilder(config, type);</span><br><span class="line">            <span class="comment">// 添加相关配置</span></span><br><span class="line">            parser.parse();</span><br><span class="line">            loadCompleted = <span class="keyword">true</span>;</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="comment">// 如果因为异常导致加载失败，则移除已经载入的接口</span></span><br><span class="line">            <span class="keyword">if</span> (!loadCompleted) &#123;</span><br><span class="line">                knownMappers.remove(type);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这里又涉及到了两个类：<code>MapperProxyFactory&lt;&gt;</code>与<code>MapperAnnotationBuilder</code>，这两个类都与 Mapper 接口生成实现类有关：</p>
<ul>
<li>前者负责通过代理创建接口实现类；</li>
<li>后者扫描代码中的注解并且加载 XXXMapper.xml 的映射文件。</li>
</ul>
<p>后者在下文就会讲到，前者将在实现类部分的文章细说。总而言之，至此所有 Xml 文件中指定的 Mapper 接口都加载完毕了。</p>
<h2 id="二-映射文件的解析">二、映射文件的解析</h2>
<p><img src="http://img.xiajibagao.top/image-20210619202948385.png" alt="image-20210619202948385"></p>
<h3 id="1标签与对象之间的关系">1.标签与对象之间的关系</h3>
<p>我们以最简单的映射文件配置为例：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">mapper</span> <span class="attr">namespace</span>=<span class="string">&quot;com.huang.read.base.PersonDao&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectByPrimaryKey&quot;</span> <span class="attr">parameterType</span>=<span class="string">&quot;java.lang.Integer&quot;</span> <span class="attr">resultMap</span>=<span class="string">&quot;BaseResultMap&quot;</span>&gt;</span></span><br><span class="line">        select</span><br><span class="line">        id, `name`</span><br><span class="line">        from Person</span><br><span class="line">        where id = #&#123;id,jdbcType=INTEGER&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">mapper</span>&gt;</span></span><br></pre></td></tr></table></figure>
<ul>
<li>接口<code>Mapper</code>：一个映射文件对应，也就是 <code>&lt;mapper&gt;&lt;/mapper&gt;</code>对应一个 Mapper 接口类，以<code>namespace</code>作为唯一标识；</li>
<li>方法声明<code>MappedStatement</code>：一个完整的方法配置块——比如上面的 <code>&lt;select&gt;&lt;/select&gt;</code> ——对应的 Mybatis 的一个 <code>MappedStatement</code>；</li>
<li>绑定 SQL <code>BoundSql</code>：一段方法声明中的 sql 片段对应 Mybatis 的一个<code>BoundSql</code>。</li>
</ul>
<p>所有的映射文件最终都会被解析为上述三个对象。</p>
<h3 id="2mapperannotationbuilder">2.MapperAnnotationBuilder</h3>
<p><strong>只有 package 或者 class 的配置方式才会进入这个类，否则像 url 或者 resources 会直接从 XMLMapperBuilder 开始。</strong></p>
<p><code>MapperAnnotationBuilder</code>这个类虽然听名是解析注解的，其实由于 mybaits 支持<code>@Select</code>这样注解式的 sql ，因此<strong>解析映射文件与绑定接口</strong>也被放在这里一起处理。</p>
<p>我们先看看他的构造方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">MapperAnnotationBuilder</span><span class="params">(Configuration configuration, Class&lt;?&gt; type)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 将 xx.xx.xx 格式转为 xx/xx/xx.java (best guess) 格式</span></span><br><span class="line">    String resource = type.getName().replace(<span class="string">&#x27;.&#x27;</span>, <span class="string">&#x27;/&#x27;</span>) + <span class="string">&quot;.java (best guess)&quot;</span>;</span><br><span class="line">    <span class="comment">// 生成Mapper构建辅助类</span></span><br><span class="line">    <span class="keyword">this</span>.assistant = <span class="keyword">new</span> MapperBuilderAssistant(configuration, resource);</span><br><span class="line">    <span class="comment">// 设置全局配置类</span></span><br><span class="line">    <span class="keyword">this</span>.configuration = configuration;</span><br><span class="line">    <span class="comment">// 设置类型为当前接口类型</span></span><br><span class="line">    <span class="keyword">this</span>.type = type;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<blockquote>
<p><code>MapperBuilderAssistant</code>这个类主要作用是解析映射文件中的标签，并将创建为对应的对象，这里我们暂时不过多研究，了解作用即可。</p>
</blockquote>
<p>接着是入口方法<code>parse()</code>，这也是一个扫描配置项-初始化为配置类的一次性过程：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 加载所有映射文件及相关注解</span></span><br><span class="line">MapperAnnotationBuilder parser = <span class="keyword">new</span> MapperAnnotationBuilder(config, type);</span><br><span class="line"><span class="comment">// 转为配置文件</span></span><br><span class="line">parser.parse();</span><br></pre></td></tr></table></figure>
<p>完成初始化后，调用<code>parse()</code>方法将配置信息存入配置类：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">parse</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    String resource = type.toString();</span><br><span class="line">    <span class="comment">// 防止重复加载</span></span><br><span class="line">    <span class="keyword">if</span> (!configuration.isResourceLoaded(resource)) &#123;</span><br><span class="line">        <span class="comment">// 加载映射文件信息</span></span><br><span class="line">        loadXmlResource();</span><br><span class="line">        configuration.addLoadedResource(resource);</span><br><span class="line">        <span class="comment">// 设置映射文件与接口对应的Namespace</span></span><br><span class="line">        assistant.setCurrentNamespace(type.getName());</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 加载二级缓存配置信息</span></span><br><span class="line">        parseCache();</span><br><span class="line">        parseCacheRef();</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 遍历接口中的方法</span></span><br><span class="line">        <span class="keyword">for</span> (Method method : type.getMethods()) &#123;</span><br><span class="line">            <span class="comment">// 检查该方法是没有对应的sql</span></span><br><span class="line">            <span class="keyword">if</span> (!canHaveStatement(method)) &#123;</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 获取方法上的注解</span></span><br><span class="line">            <span class="keyword">if</span> (getAnnotationWrapper(method, <span class="keyword">false</span>, Select.class, SelectProvider.class).isPresent()</span><br><span class="line">                &amp;&amp; method.getAnnotation(ResultMap.class) == <span class="keyword">null</span>) &#123;</span><br><span class="line">                parseResultMap(method);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// 将statement添加到配置</span></span><br><span class="line">                parseStatement(method);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (IncompleteElementException e) &#123;</span><br><span class="line">                configuration.addIncompleteMethod(<span class="keyword">new</span> MethodResolver(<span class="keyword">this</span>, method));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    parsePendingMethods();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>由于 Mybatis 的缓存与注解不影响主体逻辑，因此这里暂时先忽略。</p>
<h3 id="3xmlmapperbuilder">3.XMLMapperBuilder</h3>
<p>元素文件与接口的绑定主要逻辑在于<code>loadXmlResource()</code>方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">loadXmlResource</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// Spring may not know the real resource name so we check a flag</span></span><br><span class="line">    <span class="comment">// to prevent loading again a resource twice</span></span><br><span class="line">    <span class="comment">// this flag is set at XMLMapperBuilder#bindMapperForNamespace</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 是否已经被加载过</span></span><br><span class="line">    <span class="keyword">if</span> (!configuration.isResourceLoaded(<span class="string">&quot;namespace:&quot;</span> + type.getName())) &#123;</span><br><span class="line">        <span class="comment">// 将 xxx.xxx.xxx 的接口全限定名替换为 xxx/xxx/xxx.xml，获取对应的xml文件</span></span><br><span class="line">        String xmlResource = type.getName().replace(<span class="string">&#x27;.&#x27;</span>, <span class="string">&#x27;/&#x27;</span>) + <span class="string">&quot;.xml&quot;</span>;</span><br><span class="line">        <span class="comment">// 如果 xxx/xxx/xxx.xml 不行，换成 /xxx/xxx/xxx.xml 再试试</span></span><br><span class="line">        InputStream inputStream = type.getResourceAsStream(<span class="string">&quot;/&quot;</span> + xmlResource);</span><br><span class="line">        <span class="keyword">if</span> (inputStream == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="comment">// Search XML mapper that is not in the module but in the classpath.</span></span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// 如果加载不到，就通过相对路径找</span></span><br><span class="line">                inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (IOException e2) &#123;</span><br><span class="line">                <span class="comment">// ignore, resource is not required</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (inputStream != <span class="keyword">null</span>) &#123;</span><br><span class="line">            XMLMapperBuilder xmlParser = <span class="keyword">new</span> XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());</span><br><span class="line">            xmlParser.parse();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这里的 <code>XMLMapperBuilder</code>实际上跟 <code>XMLConfigBuilder</code>作用类似，最后也是通过 <code>XPathParser</code> 工具类去解析 Xml 文件，只不过<code>XMLMapperBuilder</code>解析的是映射文件而不是配置文件。</p>
<p>我们查看它的入口方法 <code>parse()</code> ：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">parse</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 检查配置文件是否被加载过</span></span><br><span class="line">    <span class="keyword">if</span> (!configuration.isResourceLoaded(resource)) &#123;</span><br><span class="line">        <span class="comment">// 解析根节点mapper标签，然后标记为已解析</span></span><br><span class="line">        <span class="comment">// 注意，方法声明在这一步进行解析</span></span><br><span class="line">        configurationElement(parser.evalNode(<span class="string">&quot;/mapper&quot;</span>));</span><br><span class="line">        configuration.addLoadedResource(resource);</span><br><span class="line">        <span class="comment">// 解析映射文件，并且绑定到接口对应的Namespace</span></span><br><span class="line">        bindMapperForNamespace();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 解析ResultMap</span></span><br><span class="line">    parsePendingResultMaps();</span><br><span class="line">    <span class="comment">// 解析二级缓存</span></span><br><span class="line">    parsePendingCacheRefs();</span><br><span class="line">    <span class="comment">// 转为Mapper中的方法声明</span></span><br><span class="line">    parsePendingStatements();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="4bindmapperfornamespace-方法">4.bindMapperForNamespace 方法</h3>
<p><code>parse()</code>里调用了 <code>bindMapperForNamespace()</code>  方法，这里根据映射文件的 namespace 属性把映射文件与接口绑定起来了：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">bindMapperForNamespace</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 获取配置文件中的namespace属性</span></span><br><span class="line">    String namespace = builderAssistant.getCurrentNamespace();</span><br><span class="line">    <span class="keyword">if</span> (namespace != <span class="keyword">null</span>) &#123;</span><br><span class="line">        Class&lt;?&gt; boundType = <span class="keyword">null</span>;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 根据配置文件找到接口类</span></span><br><span class="line">            boundType = Resources.classForName(namespace);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (ClassNotFoundException e) &#123;</span><br><span class="line">            <span class="comment">// ignore, bound type is not required</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 如果这个接口类还未加载过，则加载进全局配置类的映射注册表MapperRegistry中</span></span><br><span class="line">        <span class="keyword">if</span> (boundType != <span class="keyword">null</span> &amp;&amp; !configuration.hasMapper(boundType)) &#123;</span><br><span class="line">            <span class="comment">// Spring may not know the real resource name so we set a flag</span></span><br><span class="line">            <span class="comment">// to prevent loading again this resource from the mapper interface</span></span><br><span class="line">            <span class="comment">// look at MapperAnnotationBuilder#loadXmlResource</span></span><br><span class="line">            configuration.addLoadedResource(<span class="string">&quot;namespace:&quot;</span> + namespace);</span><br><span class="line">            configuration.addMapper(boundType);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这里的 <code>addMapper()</code>方法其实还是要往 <code>MapperRegister</code>里面注册接口。也就是说<strong>同一个接口实际上可能会被注册两次</strong>，在 <code>XMLConfigBuilder</code>的 <code>mapperElement()</code>里注册一次，在现在 <code>XMLMapperBuilder</code>的<code>bindMapperForNamespace()</code>里面再注册一次，这也是<strong>为什么第二次注册需要检查接口是否重复注册的原因</strong>了。</p>
<h3 id="5parsependingstatements-方法">5.parsePendingStatements 方法</h3>
<p>映射文件里的标签声明的 sql 方法在<code>XMLMapperBuilder.parse()</code>中的<code>parsePendingStatements()</code>方法被解析对应的 statement 对象:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">parsePendingStatements</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 获取所有映射文件中的方法声明</span></span><br><span class="line">    Collection&lt;XMLStatementBuilder&gt; incompleteStatements = configuration.getIncompleteStatements();</span><br><span class="line">    <span class="keyword">synchronized</span> (incompleteStatements) &#123;</span><br><span class="line">        Iterator&lt;XMLStatementBuilder&gt; iter = incompleteStatements.iterator();</span><br><span class="line">        <span class="keyword">while</span> (iter.hasNext()) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// 遍历并转换为Statement对象</span></span><br><span class="line">                iter.next().parseStatementNode();</span><br><span class="line">                iter.remove();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (IncompleteElementException e) &#123;</span><br><span class="line">                <span class="comment">// Statement is still missing a resource...</span></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>对应的转换方法为：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">XMLStatementBuilder</span> <span class="keyword">extends</span> <span class="title">BaseBuilder</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">parseStatementNode</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 获得 id 属性</span></span><br><span class="line">        String id = context.getStringAttribute(<span class="string">&quot;id&quot;</span>);</span><br><span class="line">        String databaseId = context.getStringAttribute(<span class="string">&quot;databaseId&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (!databaseIdMatchesCurrent(id, databaseId, <span class="keyword">this</span>.requiredDatabaseId)) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        String nodeName = context.getNode().getNodeName();</span><br><span class="line">        <span class="comment">// 判断sql类型（select，insert，update，delete）</span></span><br><span class="line">        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));</span><br><span class="line">        <span class="comment">// 是否为select语句</span></span><br><span class="line">        <span class="keyword">boolean</span> isSelect = sqlCommandType == SqlCommandType.SELECT;</span><br><span class="line">        <span class="comment">// 如果不是select，是否清空缓存</span></span><br><span class="line">        <span class="keyword">boolean</span> flushCache = context.getBooleanAttribute(<span class="string">&quot;flushCache&quot;</span>, !isSelect);</span><br><span class="line">        <span class="comment">// 如果是select，是否使用缓存</span></span><br><span class="line">        <span class="keyword">boolean</span> useCache = context.getBooleanAttribute(<span class="string">&quot;useCache&quot;</span>, isSelect);</span><br><span class="line">        <span class="keyword">boolean</span> resultOrdered = context.getBooleanAttribute(<span class="string">&quot;resultOrdered&quot;</span>, <span class="keyword">false</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Include Fragments before parsing</span></span><br><span class="line">        XMLIncludeTransformer includeParser = <span class="keyword">new</span> XMLIncludeTransformer(configuration, builderAssistant);</span><br><span class="line">        <span class="comment">// 将&lt;include /&gt;转换对应的&lt;sql /&gt;节点</span></span><br><span class="line">        includeParser.applyIncludes(context.getNode());</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 获取参数类型名称并转为对应java类型</span></span><br><span class="line">        String parameterType = context.getStringAttribute(<span class="string">&quot;parameterType&quot;</span>);</span><br><span class="line">        Class&lt;?&gt; parameterTypeClass = resolveClass(parameterType);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 获得对应驱动脚本</span></span><br><span class="line">        String lang = context.getStringAttribute(<span class="string">&quot;lang&quot;</span>);</span><br><span class="line">        LanguageDriver langDriver = getLanguageDriver(lang);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Parse selectKey after includes and remove them.</span></span><br><span class="line">        <span class="comment">// 将&lt;selectKey /&gt;解析成selectKeyGenerator生成器</span></span><br><span class="line">        processSelectKeyNodes(id, parameterTypeClass, langDriver);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Parse the SQL (pre: &lt;selectKey&gt; and &lt;include&gt; were parsed and removed)</span></span><br><span class="line">        KeyGenerator keyGenerator;</span><br><span class="line">        String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;</span><br><span class="line">        keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, <span class="keyword">true</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (configuration.hasKeyGenerator(keyStatementId)) &#123;</span><br><span class="line">            <span class="comment">// 如果上面存在&lt;selectKey /&gt;子节点，则获取上面对其解析后生成的selectKeyGenerator</span></span><br><span class="line">            keyGenerator = configuration.getKeyGenerator(keyStatementId);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 如果配置了useGeneratedKeys并且为insert语句，就使用Jdbc3KeyGenerator</span></span><br><span class="line">            keyGenerator = context.getBooleanAttribute(<span class="string">&quot;useGeneratedKeys&quot;</span>,</span><br><span class="line">                                                       configuration.isUseGeneratedKeys() &amp;&amp; SqlCommandType.INSERT.equals(sqlCommandType))</span><br><span class="line">                ? Jdbc3KeyGenerator.INSTANCE</span><br><span class="line">                : NoKeyGenerator.INSTANCE;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 创建对应的 SqlSource 对象，保存了该节点下 SQL 相关信息</span></span><br><span class="line">        SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);</span><br><span class="line">        <span class="comment">// 获得statement类型，默认为PREPARED</span></span><br><span class="line">        StatementType statementType = StatementType.valueOf(context.getStringAttribute(<span class="string">&quot;statementType&quot;</span>, StatementType.PREPARED.toString()));</span><br><span class="line">        Integer fetchSize = context.getIntAttribute(<span class="string">&quot;fetchSize&quot;</span>);</span><br><span class="line">        Integer timeout = context.getIntAttribute(<span class="string">&quot;timeout&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 获取入参与返回值类型</span></span><br><span class="line">        String parameterMap = context.getStringAttribute(<span class="string">&quot;parameterMap&quot;</span>);</span><br><span class="line">        String resultType = context.getStringAttribute(<span class="string">&quot;resultType&quot;</span>);</span><br><span class="line">        Class&lt;?&gt; resultTypeClass = resolveClass(resultType);</span><br><span class="line">        String resultMap = context.getStringAttribute(<span class="string">&quot;resultMap&quot;</span>);</span><br><span class="line">        String resultSetType = context.getStringAttribute(<span class="string">&quot;resultSetType&quot;</span>);</span><br><span class="line">        ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);</span><br><span class="line">        <span class="keyword">if</span> (resultSetTypeEnum == <span class="keyword">null</span>) &#123;</span><br><span class="line">            resultSetTypeEnum = configuration.getDefaultResultSetType();</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 获取useGeneratedKeys需要的相关属性</span></span><br><span class="line">        String keyProperty = context.getStringAttribute(<span class="string">&quot;keyProperty&quot;</span>);</span><br><span class="line">        String keyColumn = context.getStringAttribute(<span class="string">&quot;keyColumn&quot;</span>);</span><br><span class="line">        String resultSets = context.getStringAttribute(<span class="string">&quot;resultSets&quot;</span>);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 根据以上信息生成一个MappedStatement对象</span></span><br><span class="line">        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout,</span><br><span class="line">                                            parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache,</span><br><span class="line">                                            resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>关于方法声明标签中一些属性的具体作用，可以参考<a target="_blank" rel="noopener" href="https://juejin.cn/post/6844904143392358407">Mybatis源码之美:3.7.深入了解select元素</a>。</p>
<p>接着，在 <code>MapperBuilderAssistant.addMappedStatement()</code>中，经过一系列的处理：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> MappedStatement <span class="title">addMappedStatement</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params">    String id,</span></span></span><br><span class="line"><span class="function"><span class="params">    SqlSource sqlSource,</span></span></span><br><span class="line"><span class="function"><span class="params">    StatementType statementType,</span></span></span><br><span class="line"><span class="function"><span class="params">    SqlCommandType sqlCommandType,</span></span></span><br><span class="line"><span class="function"><span class="params">    Integer fetchSize,</span></span></span><br><span class="line"><span class="function"><span class="params">    Integer timeout,</span></span></span><br><span class="line"><span class="function"><span class="params">    String parameterMap,</span></span></span><br><span class="line"><span class="function"><span class="params">    Class&lt;?&gt; parameterType,</span></span></span><br><span class="line"><span class="function"><span class="params">    String resultMap,</span></span></span><br><span class="line"><span class="function"><span class="params">    Class&lt;?&gt; resultType,</span></span></span><br><span class="line"><span class="function"><span class="params">    ResultSetType resultSetType,</span></span></span><br><span class="line"><span class="function"><span class="params">    <span class="keyword">boolean</span> flushCache,</span></span></span><br><span class="line"><span class="function"><span class="params">    <span class="keyword">boolean</span> useCache,</span></span></span><br><span class="line"><span class="function"><span class="params">    <span class="keyword">boolean</span> resultOrdered,</span></span></span><br><span class="line"><span class="function"><span class="params">    KeyGenerator keyGenerator,</span></span></span><br><span class="line"><span class="function"><span class="params">    String keyProperty,</span></span></span><br><span class="line"><span class="function"><span class="params">    String keyColumn,</span></span></span><br><span class="line"><span class="function"><span class="params">    String databaseId,</span></span></span><br><span class="line"><span class="function"><span class="params">    LanguageDriver lang,</span></span></span><br><span class="line"><span class="function"><span class="params">    String resultSets)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (unresolvedCacheRef) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> IncompleteElementException(<span class="string">&quot;Cache-ref not yet resolved&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    id = applyCurrentNamespace(id, <span class="keyword">false</span>);</span><br><span class="line">    <span class="keyword">boolean</span> isSelect = sqlCommandType == SqlCommandType.SELECT;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 构建一个MappedStatement</span></span><br><span class="line">    MappedStatement.Builder statementBuilder = <span class="keyword">new</span> MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)</span><br><span class="line">        .resource(resource)</span><br><span class="line">        .fetchSize(fetchSize)</span><br><span class="line">        .timeout(timeout)</span><br><span class="line">        .statementType(statementType)</span><br><span class="line">        .keyGenerator(keyGenerator)</span><br><span class="line">        .keyProperty(keyProperty)</span><br><span class="line">        .keyColumn(keyColumn)</span><br><span class="line">        .databaseId(databaseId)</span><br><span class="line">        .lang(lang)</span><br><span class="line">        .resultOrdered(resultOrdered)</span><br><span class="line">        .resultSets(resultSets)</span><br><span class="line">        .resultMaps(getStatementResultMaps(resultMap, resultType, id))</span><br><span class="line">        .resultSetType(resultSetType)</span><br><span class="line">        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))</span><br><span class="line">        .useCache(valueOrDefault(useCache, isSelect))</span><br><span class="line">        .cache(currentCache);</span><br><span class="line"></span><br><span class="line">    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);</span><br><span class="line">    <span class="keyword">if</span> (statementParameterMap != <span class="keyword">null</span>) &#123;</span><br><span class="line">        statementBuilder.parameterMap(statementParameterMap);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 加入 Configuration中的</span></span><br><span class="line">    MappedStatement statement = statementBuilder.build();</span><br><span class="line">    configuration.addMappedStatement(statement);</span><br><span class="line">    <span class="keyword">return</span> statement;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>最终生成的<code>MappedStatement</code>会以方法声明的 id 作为标识，加入全局配置类中的 Map 集合<code>mappedStatements</code>中：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addMappedStatement</span><span class="params">(MappedStatement ms)</span> </span>&#123;</span><br><span class="line">    mappedStatements.put(ms.getId(), ms);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// StrictMap是一个继承了HashMap的自定义集合，针对Myabtis存放MappedStatement的需求针对方法做了一些调整</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">final</span> Map&lt;String, MappedStatement&gt; mappedStatements = <span class="keyword">new</span> StrictMap&lt;MappedStatement&gt;(<span class="string">&quot;Mapped Statements collection&quot;</span>)</span><br><span class="line">    .conflictMessageProducer((savedValue, targetValue) -&gt;</span><br><span class="line">                             <span class="string">&quot;. please check &quot;</span> + savedValue.getResource() + <span class="string">&quot; and &quot;</span> + targetValue.getResource());</span><br></pre></td></tr></table></figure>
<h3 id="6mappedstatement">6.MappedStatement</h3>
<p><code>MappedStatement</code>对应了映射文件中的一个方法声明，每个<code>MappedStatement</code>对象都以 <code>namespace.id</code>的形式区分，里面包含了映射文件中一个方法签名所有必要的数据，包括 sql 对象，映射文件路径，入参和返回值类型等等。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">MappedStatement</span> </span>&#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 映射文件路径</span></span><br><span class="line">    <span class="keyword">private</span> String resource;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 配置类</span></span><br><span class="line">    <span class="keyword">private</span> Configuration configuration;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 方法声明的全限定名 namespace.id</span></span><br><span class="line">    <span class="keyword">private</span> String id;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 批量查询行数</span></span><br><span class="line">    <span class="keyword">private</span> Integer fetchSize;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 超时时间</span></span><br><span class="line">    <span class="keyword">private</span> Integer timeout;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// statement类型，有STATEMENT PREPARED CALLABLE三种，分别对应不同的Statement</span></span><br><span class="line">    <span class="keyword">private</span> StatementType statementType;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 返回值类型</span></span><br><span class="line">    <span class="keyword">private</span> ResultSetType resultSetType;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// sql对象获取方法</span></span><br><span class="line">    <span class="keyword">private</span> SqlSource sqlSource;</span><br><span class="line">    <span class="comment">// 语言驱动</span></span><br><span class="line">    <span class="keyword">private</span> LanguageDriver lang;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 缓存对象</span></span><br><span class="line">    <span class="keyword">private</span> Cache cache;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 执行后是否清空缓存</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">boolean</span> flushCacheRequired;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 是否使用缓存</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">boolean</span> useCache;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 入参类型</span></span><br><span class="line">    <span class="keyword">private</span> ParameterMap parameterMap;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// ResultMap</span></span><br><span class="line">    <span class="keyword">private</span> List&lt;ResultMap&gt; resultMaps;</span><br><span class="line">    <span class="comment">// 是否有嵌套的ResultMap</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">boolean</span> hasNestedResultMaps;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">boolean</span> resultOrdered;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// sql类型</span></span><br><span class="line">    <span class="keyword">private</span> SqlCommandType sqlCommandType;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// keuy生成器</span></span><br><span class="line">    <span class="keyword">private</span> KeyGenerator keyGenerator;</span><br><span class="line">    <span class="keyword">private</span> String[] keyProperties;</span><br><span class="line">    <span class="keyword">private</span> String[] keyColumns;</span><br><span class="line">    </span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 数据库标识</span></span><br><span class="line">    <span class="keyword">private</span> String databaseId;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 日志</span></span><br><span class="line">    <span class="keyword">private</span> Log statementLog;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 多结果集</span></span><br><span class="line">    <span class="keyword">private</span> String[] resultSets;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="三-关于接口与映射文件关系的思考">三、关于接口与映射文件关系的思考</h2>
<h3 id="1先加载接口还是映射文件">1.先加载接口还是映射文件？</h3>
<p>关于这个问题，我们回头看看 <code>XMLConfigBuilder</code> 里的 <code>mapperElement()</code>，其实就能理明白接口与映射文件的加载顺序。</p>
<p>首先，Myabtis 的四种配置方式，其实本质上对应着两种顺序：</p>
<ul>
<li><strong>先找接口，再根据接口全限定名找映射文件</strong>：<code>&lt;package&gt;</code>和 <code>&lt;mapper class=&quot;xxx.xxx.xxx&quot;&gt;</code>；</li>
<li><strong>先找映射文件，再根据 namespace 找接口</strong>： <code>&lt;mapper url=&quot;xxx/xxx/xxx.xml&quot;&gt;</code> 和  <code>&lt;mapper resource=&quot;xxx/xxx/xxx.xml&quot;&gt;</code></li>
</ul>
<p><strong>先找接口</strong></p>
<ol>
<li>
<p>直接先在指定位置找到接口类，注册到 <code>MapperRegister</code>里面；</p>
</li>
<li>
<p>在此时创建 <code>MapperAnnotationBuilder</code>，解析<code>@Select</code>这样的 sql 标签；</p>
</li>
<li>
<p>接着将接口类的全限定名 <code>com.xxx.xxx.class</code> 转成 <code>com/xxx/xxx/xml</code>作为映射文件路径，然后创建 <code>XMLMapperBuilder</code>去根据路径解析映射文件，表现出来的形式就是接口与映射文件需要同包同名；</p>
</li>
<li>
<p>解析映射文件的过程中，再根据映射文件的 namespace 属性找接口，试着再注册一次，如果已经注册过就不注册。</p>
<p>另外需要注意的是，这里如果映射文件的 namespace 跟 <code>MapperAnnotationBuilder</code>的全限定名不一样就会报错。</p>
</li>
</ol>
<p><strong>先找映射文件</strong></p>
<ol>
<li>直接根据配置文件路径找到映射文件；</li>
<li>根据映射文件 namespace 找到并且注册接口。</li>
</ol>
<p>这里我们就完全明白了：</p>
<ul>
<li>
<p>为什么<code>XMLMapperBuilder</code>解析映射文件注册接口的时候要做重复校验？</p>
<p>因为根据配置方式的不同，可能在加载映射文件之前接口已经被注册一次了；</p>
</li>
<li>
<p>为什么 package 和 class 的配置方式需要接口和映射文件同包同名？</p>
<p>因为加载完接口会直接根据接口全限定名找对应的映射文件；</p>
</li>
</ul>
<h3 id="2接口可不可以有多个映射文件">2.接口可不可以有多个映射文件？</h3>
<p>答案是<strong>可以</strong>，根据上述理论，一个接口是可以有多个映射文件的。</p>
<p>也就是说，如果有一个接口叫 <code>Person.class</code>，里面的 <code>selectOne()</code> 方法在 <code>Person.xml</code>，而  <code>selectList()</code>方法在<code>Person2.xml</code>，而两个映射文件的 namespace 都指向了 <code>Person.class</code>。</p>
<p>当我们在配置文件中以 <code>&lt;mapper url=&quot;xxx/xxx/xxx.xml&quot;&gt;</code> 或  <code>&lt;mapper resource=&quot;xxx/xxx/xxx.xml&quot;&gt;</code> 的方式配置它并运行程序，那么实际上 <code>Person.class</code>所有的方法都可以正常调用。</p>
<p>从理论上来说，<code>Person.xml</code> 和 <code>Person2.xml</code>的信息最终都会合并到一个 <code>Statement</code> 里头，这两个映射文件除了 <code>&lt;Mapper&gt;&lt;/Mapper&gt;</code>跟标签外，其他<strong>内容都是互通的</strong>，在<code>Person.xml</code>配置的 <code>&lt;Sql&gt;</code>或者 <code>&lt;ResultMap&gt;</code> 标签在 <code>Person2.xml</code>中一样可以正常使用。</p>
<h2 id="总结">总结</h2>
<p>我们总结一下 Mapper 接口与映射文件的加载过程：</p>
<ol>
<li>
<p>在 <code>XmlConfigBuilder</code> 解析 <code>&lt;mappers&gt;&lt;/ mappers&gt;</code>标签，获取要解析的 Mapper 集合</p>
<p>如果 mapper 是以 package 标签或者  mapper 的 class 属性配置的，就带着配置文件路径直接跳到步骤 4</p>
<p>如果 mapper 是以 mapper 的 resource 或者 url 属性配置的，就继续步骤 2；</p>
</li>
<li>
<p>在 <code>MapperRegister</code> 中注册接口；</p>
</li>
<li>
<p>对每一个 Mapper 接口创建一个 <code>MapperAnnotationBuilder</code>，用于解析 <code>@Select</code>这样的 sql 标签</p>
<p>将 <code>xxx.xxx.xxx</code> 的接口全限定名转为 <code>xxx/xxx/xxx.xml</code>作为步骤 4 的配置文件路径；</p>
</li>
<li>
<p>创建一个 <code>XMLMapperBUilder</code>，并根据配置文件路径找到映射文件并解析；</p>
</li>
<li>
<p>调用 <code>bindMapperForNamespace()</code>方法，解析映射文件并获取映射文件的 namespace 属性，找到并将接口注册到 <code>MapperRegister</code> 中；</p>
</li>
<li>
<p>调用 <code>parsePendingStatements()</code>方法，解析映射文件的方法声明，然后通过 <code>XMLStatementBuilder</code>转为 <code>MappedStatement</code>。</p>
</li>
</ol>

      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      
        <div class="post-tags">
          
            <a href="/tags/Mybatis/" rel="tag"># Mybatis</a>
          
        </div>
      

      
      
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/2021/06/05/mybatis/Mybatis%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%B8%80%EF%BC%89%EF%BC%9A%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E7%9A%84%E5%8A%A0%E8%BD%BD/" rel="next" title="Mybatis源码学习（一）：配置文件的加载">
                <i class="fa fa-chevron-left"></i> Mybatis源码学习（一）：配置文件的加载
              </a>
            
          </div>

          <span class="post-nav-divider"></span>

          <div class="post-nav-prev post-nav-item">
            
              <a href="/2021/07/11/mybatis/Mybatis%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%B8%89%EF%BC%89%EF%BC%9A%E6%98%A0%E5%B0%84%E6%96%87%E4%BB%B6%E4%B8%ADsql%E7%9A%84%E8%A7%A3%E6%9E%90/" rel="prev" title="Mybatis源码学习（三）：映射文件中sql的解析">
                Mybatis源码学习（三）：映射文件中sql的解析 <i class="fa fa-chevron-right"></i>
              </a>
            
          </div>
        </div>
      

      
      
    </footer>
  </div>
  
  
  
  </article>



    <div class="post-spread">
      
    </div>
  </div>


          </div>
          


          

  



        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      
        <ul class="sidebar-nav motion-element">
          <li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap">
            文章目录
          </li>
          <li class="sidebar-nav-overview" data-target="site-overview-wrap">
            站点概览
          </li>
        </ul>
      

      <section class="site-overview-wrap sidebar-panel">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
            
              <img class="site-author-image" itemprop="image"
                src="/images/Createsequence.jpg"
                alt="" />
            
              <p class="site-author-name" itemprop="name"></p>
              <p class="site-description motion-element" itemprop="description"></p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/archives/%7C%7Carchive">
              
                  <span class="site-state-item-count">89</span>
                  <span class="site-state-item-name">日志</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-categories">
                <a href="/categories/index.html">
                  <span class="site-state-item-count">15</span>
                  <span class="site-state-item-name">分类</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-tags">
                <a href="/tags/index.html">
                  <span class="site-state-item-count">21</span>
                  <span class="site-state-item-name">标签</span>
                </a>
              </div>
            

          </nav>

          
            <div class="feed-link motion-element">
              <a href="/atom.xml" rel="alternate">
                <i class="fa fa-rss"></i>
                RSS
              </a>
            </div>
          

          

          
          

          
          

          

        </div>
      </section>

      
      <!--noindex-->
        <section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
          <div class="post-toc">

            
              
            

            
              <div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%A6%82%E8%BF%B0"><span class="nav-text">概述</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%B8%80-%E5%8A%A0%E8%BD%BD%E6%8E%A5%E5%8F%A3"><span class="nav-text">一、加载接口</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#1xmlconfigbuilder"><span class="nav-text">1.XMLConfigBuilder</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2mapperregistry"><span class="nav-text">2.MapperRegistry</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%BA%8C-%E6%98%A0%E5%B0%84%E6%96%87%E4%BB%B6%E7%9A%84%E8%A7%A3%E6%9E%90"><span class="nav-text">二、映射文件的解析</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#1%E6%A0%87%E7%AD%BE%E4%B8%8E%E5%AF%B9%E8%B1%A1%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB"><span class="nav-text">1.标签与对象之间的关系</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2mapperannotationbuilder"><span class="nav-text">2.MapperAnnotationBuilder</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#3xmlmapperbuilder"><span class="nav-text">3.XMLMapperBuilder</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#4bindmapperfornamespace-%E6%96%B9%E6%B3%95"><span class="nav-text">4.bindMapperForNamespace 方法</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#5parsependingstatements-%E6%96%B9%E6%B3%95"><span class="nav-text">5.parsePendingStatements 方法</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#6mappedstatement"><span class="nav-text">6.MappedStatement</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%B8%89-%E5%85%B3%E4%BA%8E%E6%8E%A5%E5%8F%A3%E4%B8%8E%E6%98%A0%E5%B0%84%E6%96%87%E4%BB%B6%E5%85%B3%E7%B3%BB%E7%9A%84%E6%80%9D%E8%80%83"><span class="nav-text">三、关于接口与映射文件关系的思考</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#1%E5%85%88%E5%8A%A0%E8%BD%BD%E6%8E%A5%E5%8F%A3%E8%BF%98%E6%98%AF%E6%98%A0%E5%B0%84%E6%96%87%E4%BB%B6"><span class="nav-text">1.先加载接口还是映射文件？</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2%E6%8E%A5%E5%8F%A3%E5%8F%AF%E4%B8%8D%E5%8F%AF%E4%BB%A5%E6%9C%89%E5%A4%9A%E4%B8%AA%E6%98%A0%E5%B0%84%E6%96%87%E4%BB%B6"><span class="nav-text">2.接口可不可以有多个映射文件？</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E6%80%BB%E7%BB%93"><span class="nav-text">总结</span></a></li></ol></div>
            

          </div>
        </section>
      <!--/noindex-->
      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright">&copy; <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">Createsequence</span>

  
</div>






  <div class="theme-info">主题 &mdash; <a class="theme-link" target="_blank" href="https://github.com/iissnan/hexo-theme-next">NexT.Gemini</a> v5.1.4</div>




        







        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
          <span id="scrollpercent"><span>0</span>%</span>
        
      </div>
    

    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  












  
  
    <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>
  

  
  
    <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
  

  
  
    <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
  


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.4"></script>



  
  


  <script type="text/javascript" src="/js/src/affix.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/schemes/pisces.js?v=5.1.4"></script>



  
  <script type="text/javascript" src="/js/src/scrollspy.js?v=5.1.4"></script>
<script type="text/javascript" src="/js/src/post-details.js?v=5.1.4"></script>



  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.4"></script>



  


  




	





  





  












  

  <script type="text/javascript">
    // Popup Window;
    var isfetched = false;
    var isXml = true;
    // Search DB path;
    var search_path = "search.xml";
    if (search_path.length === 0) {
      search_path = "search.xml";
    } else if (/json$/i.test(search_path)) {
      isXml = false;
    }
    var path = "/" + search_path;
    // monitor main search box;

    var onPopupClose = function (e) {
      $('.popup').hide();
      $('#local-search-input').val('');
      $('.search-result-list').remove();
      $('#no-result').remove();
      $(".local-search-pop-overlay").remove();
      $('body').css('overflow', '');
    }

    function proceedsearch() {
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay"></div>')
        .css('overflow', 'hidden');
      $('.search-popup-overlay').click(onPopupClose);
      $('.popup').toggle();
      var $localSearchInput = $('#local-search-input');
      $localSearchInput.attr("autocapitalize", "none");
      $localSearchInput.attr("autocorrect", "off");
      $localSearchInput.focus();
    }

    // search function;
    var searchFunc = function(path, search_id, content_id) {
      'use strict';

      // start loading animation
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay">' +
          '<div id="search-loading-icon">' +
          '<i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>' +
          '</div>' +
          '</div>')
        .css('overflow', 'hidden');
      $("#search-loading-icon").css('margin', '20% auto 0 auto').css('text-align', 'center');

      $.ajax({
        url: path,
        dataType: isXml ? "xml" : "json",
        async: true,
        success: function(res) {
          // get the contents from search data
          isfetched = true;
          $('.popup').detach().appendTo('.header-inner');
          var datas = isXml ? $("entry", res).map(function() {
            return {
              title: $("title", this).text(),
              content: $("content",this).text(),
              url: $("url" , this).text()
            };
          }).get() : res;
          var input = document.getElementById(search_id);
          var resultContent = document.getElementById(content_id);
          var inputEventFunction = function() {
            var searchText = input.value.trim().toLowerCase();
            var keywords = searchText.split(/[\s\-]+/);
            if (keywords.length > 1) {
              keywords.push(searchText);
            }
            var resultItems = [];
            if (searchText.length > 0) {
              // perform local searching
              datas.forEach(function(data) {
                var isMatch = false;
                var hitCount = 0;
                var searchTextCount = 0;
                var title = data.title.trim();
                var titleInLowerCase = title.toLowerCase();
                var content = data.content.trim().replace(/<[^>]+>/g,"");
                var contentInLowerCase = content.toLowerCase();
                var articleUrl = decodeURIComponent(data.url);
                var indexOfTitle = [];
                var indexOfContent = [];
                // only match articles with not empty titles
                if(title != '') {
                  keywords.forEach(function(keyword) {
                    function getIndexByWord(word, text, caseSensitive) {
                      var wordLen = word.length;
                      if (wordLen === 0) {
                        return [];
                      }
                      var startPosition = 0, position = [], index = [];
                      if (!caseSensitive) {
                        text = text.toLowerCase();
                        word = word.toLowerCase();
                      }
                      while ((position = text.indexOf(word, startPosition)) > -1) {
                        index.push({position: position, word: word});
                        startPosition = position + wordLen;
                      }
                      return index;
                    }

                    indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false));
                    indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false));
                  });
                  if (indexOfTitle.length > 0 || indexOfContent.length > 0) {
                    isMatch = true;
                    hitCount = indexOfTitle.length + indexOfContent.length;
                  }
                }

                // show search results

                if (isMatch) {
                  // sort index by position of keyword

                  [indexOfTitle, indexOfContent].forEach(function (index) {
                    index.sort(function (itemLeft, itemRight) {
                      if (itemRight.position !== itemLeft.position) {
                        return itemRight.position - itemLeft.position;
                      } else {
                        return itemLeft.word.length - itemRight.word.length;
                      }
                    });
                  });

                  // merge hits into slices

                  function mergeIntoSlice(text, start, end, index) {
                    var item = index[index.length - 1];
                    var position = item.position;
                    var word = item.word;
                    var hits = [];
                    var searchTextCountInSlice = 0;
                    while (position + word.length <= end && index.length != 0) {
                      if (word === searchText) {
                        searchTextCountInSlice++;
                      }
                      hits.push({position: position, length: word.length});
                      var wordEnd = position + word.length;

                      // move to next position of hit

                      index.pop();
                      while (index.length != 0) {
                        item = index[index.length - 1];
                        position = item.position;
                        word = item.word;
                        if (wordEnd > position) {
                          index.pop();
                        } else {
                          break;
                        }
                      }
                    }
                    searchTextCount += searchTextCountInSlice;
                    return {
                      hits: hits,
                      start: start,
                      end: end,
                      searchTextCount: searchTextCountInSlice
                    };
                  }

                  var slicesOfTitle = [];
                  if (indexOfTitle.length != 0) {
                    slicesOfTitle.push(mergeIntoSlice(title, 0, title.length, indexOfTitle));
                  }

                  var slicesOfContent = [];
                  while (indexOfContent.length != 0) {
                    var item = indexOfContent[indexOfContent.length - 1];
                    var position = item.position;
                    var word = item.word;
                    // cut out 100 characters
                    var start = position - 20;
                    var end = position + 80;
                    if(start < 0){
                      start = 0;
                    }
                    if (end < position + word.length) {
                      end = position + word.length;
                    }
                    if(end > content.length){
                      end = content.length;
                    }
                    slicesOfContent.push(mergeIntoSlice(content, start, end, indexOfContent));
                  }

                  // sort slices in content by search text's count and hits' count

                  slicesOfContent.sort(function (sliceLeft, sliceRight) {
                    if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) {
                      return sliceRight.searchTextCount - sliceLeft.searchTextCount;
                    } else if (sliceLeft.hits.length !== sliceRight.hits.length) {
                      return sliceRight.hits.length - sliceLeft.hits.length;
                    } else {
                      return sliceLeft.start - sliceRight.start;
                    }
                  });

                  // select top N slices in content

                  var upperBound = parseInt('1');
                  if (upperBound >= 0) {
                    slicesOfContent = slicesOfContent.slice(0, upperBound);
                  }

                  // highlight title and content

                  function highlightKeyword(text, slice) {
                    var result = '';
                    var prevEnd = slice.start;
                    slice.hits.forEach(function (hit) {
                      result += text.substring(prevEnd, hit.position);
                      var end = hit.position + hit.length;
                      result += '<b class="search-keyword">' + text.substring(hit.position, end) + '</b>';
                      prevEnd = end;
                    });
                    result += text.substring(prevEnd, slice.end);
                    return result;
                  }

                  var resultItem = '';

                  if (slicesOfTitle.length != 0) {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + highlightKeyword(title, slicesOfTitle[0]) + "</a>";
                  } else {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + title + "</a>";
                  }

                  slicesOfContent.forEach(function (slice) {
                    resultItem += "<a href='" + articleUrl + "'>" +
                      "<p class=\"search-result\">" + highlightKeyword(content, slice) +
                      "...</p>" + "</a>";
                  });

                  resultItem += "</li>";
                  resultItems.push({
                    item: resultItem,
                    searchTextCount: searchTextCount,
                    hitCount: hitCount,
                    id: resultItems.length
                  });
                }
              })
            };
            if (keywords.length === 1 && keywords[0] === "") {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-search fa-5x" /></div>'
            } else if (resultItems.length === 0) {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-frown-o fa-5x" /></div>'
            } else {
              resultItems.sort(function (resultLeft, resultRight) {
                if (resultLeft.searchTextCount !== resultRight.searchTextCount) {
                  return resultRight.searchTextCount - resultLeft.searchTextCount;
                } else if (resultLeft.hitCount !== resultRight.hitCount) {
                  return resultRight.hitCount - resultLeft.hitCount;
                } else {
                  return resultRight.id - resultLeft.id;
                }
              });
              var searchResultList = '<ul class=\"search-result-list\">';
              resultItems.forEach(function (result) {
                searchResultList += result.item;
              })
              searchResultList += "</ul>";
              resultContent.innerHTML = searchResultList;
            }
          }

          if ('auto' === 'auto') {
            input.addEventListener('input', inputEventFunction);
          } else {
            $('.search-icon').click(inputEventFunction);
            input.addEventListener('keypress', function (event) {
              if (event.keyCode === 13) {
                inputEventFunction();
              }
            });
          }

          // remove loading animation
          $(".local-search-pop-overlay").remove();
          $('body').css('overflow', '');

          proceedsearch();
        }
      });
    }

    // handle and trigger popup window;
    $('.popup-trigger').click(function(e) {
      e.stopPropagation();
      if (isfetched === false) {
        searchFunc(path, 'local-search-input', 'local-search-result');
      } else {
        proceedsearch();
      };
    });

    $('.popup-btn-close').click(onPopupClose);
    $('.popup').click(function(e){
      e.stopPropagation();
    });
    $(document).on('keyup', function (event) {
      var shouldDismissSearchPopup = event.which === 27 &&
        $('.search-popup').is(':visible');
      if (shouldDismissSearchPopup) {
        onPopupClose();
      }
    });
  </script>





  

  

  

  
  

  

  

  


  <!-- 引入目录截取js -->
  <script type="text/javascript" src="/js/src/custom/custom.js"></script>
</body>
</html>
